package bloodfang;
import java.util.Arrays;

import bloodfang.Settings.SubtletyCPG;
import bloodfang.data.KeyValueStore;

public class CycleSubtlety extends CycleRogue {

	private double find_weakness_uptime;
	private double find_weakness_damage_boost = 1.;
	private double ambushes_from_vanish = 0.;
	private double mos_multiplier = 1.;
	private double find_weakness_multiplier = 1.;
	
	private double hemo_duration = 24.;
	private double sd_duration = 8.;
	
	private double current_sd_cd = shadow_dance_cd;
	private double current_vanish_cd = vanish_cd;

	public CycleSubtlety(Stats stats, Attacks attacks) {
		this.stats = stats;
		this.attacks = attacks;
		this.stats.getSettings().spec = Spec.SUBTLETY;
		if(stats.getMh().getType() != WeaponType.DAGGER){
			System.out.println("INFO: Auto-switch to hemorrhage only because using non-dagger weapon in MH");
			stats.getSettings().subtlety_cpg = SubtletyCPG.HEMORRHAGE_ONLY;
		}
	}

	@Override
	protected void generate_aps() {
		double last_aps = 0.;
		aps.clear();
		
		double base_vanish_cd = vanish_cd;
		double base_sd_cd = shadow_dance_cd;
		
		double cooldownRecoveryRate = stats.getCooldownRecoveryRate();
		base_vanish_cd /= (1. + cooldownRecoveryRate);
		base_sd_cd /= (1. + cooldownRecoveryRate);
		
		current_vanish_cd = base_vanish_cd + stats.getSettings().cooldown_delay;
		current_sd_cd = base_sd_cd + stats.getSettings().cooldown_delay;

		if(stats.getSettings().player_level >= 100) {
			// perk enhanced shadow dance
			sd_duration += 2.;
			// perk enhanced vanish
			current_vanish_cd -= 30.;
		}
		
		while(true){
			aps.clear(); // do not remove
			double energy_regen = get_energy_regen();
			
			double extra_evis_per_sec = 0.0;
			if(stats.getSettings().subtlety_cpg == SubtletyCPG.BACKSTAB_WITH_HEMO){
				double ec_hemo = stats.getRealEnergyCost(30);
				double hemo_per_sec = 1./ this.hemo_duration;
				aps.add("hemorrhage", hemo_per_sec);
				aps.add("hemorrhage_dot", 1. / 3);
				energy_regen -= hemo_per_sec * ec_hemo;
				extra_evis_per_sec += hemo_per_sec * 1. / 5.;
			}
			
			// HAT TODO better at another place
			double hat_cp_every = 2.05;
			extra_evis_per_sec += 1. / (5 * hat_cp_every);
			if(stats.hasSetBonus(SetBonus.T16_2PC)){
				energy_regen += 3 * 1 / hat_cp_every;
			}
			
			double base_evi_ec = 35.;
			// perk enhanced eviscerate
			if(stats.getSettings().player_level >= 100)
				base_evi_ec -= 5.;
			
			energy_regen -= extra_evis_per_sec * (stats.getRealEnergyCost(base_evi_ec) - 5. * 5);
			aps.add("eviscerate", extra_evis_per_sec);
			
			// VANISH
	        double prep = 1.; // baseline
	        ambushes_from_vanish = 1. / current_vanish_cd + prep / stats.getSettings().FIGHT_LENGTH;
	        find_weakness_uptime = 10 * ambushes_from_vanish;
	        
	        aps.add("ambush", ambushes_from_vanish);
	        double base_ambush_ec = 60.;
	        if(stats.hasTalent(Talent.shadow_focus))
	        	base_ambush_ec *= 0.25;
	        double ambush_ec = stats.getRealEnergyCost(base_ambush_ec);
	        energy_regen -= ambushes_from_vanish * ambush_ec;
	        
	        double cp_per_ambush = 2.; 
	        double extra_evis_from_vanish_ambush = ambushes_from_vanish * cp_per_ambush / 5.;
	        energy_regen -= extra_evis_from_vanish_ambush * (stats.getRealEnergyCost(base_evi_ec) - 5. * 5);
			aps.add("eviscerate", extra_evis_from_vanish_ambush);
			
			// we are having different cycles to manage
			// 1. shadow dance
			// 4. no shadow dance
			CycleSubtletySetup sd = new CycleSubtletySetup();
			sd.name = "shadow_dance";
			sd.length = sd_duration;
			sd.energy_regen = energy_regen;
			sd.shadow_dance = true;
			
			CycleSubtletySetup no_sd = new CycleSubtletySetup();
			no_sd.name = "filler";
			no_sd.length = current_sd_cd - sd_duration;
			no_sd.energy_regen = energy_regen;
			no_sd.shadow_dance = false;
			
			// rough line up
			// 1. dance (8sec+2sec from perk)
			// 2. no dance ( 52sec-2sec if perk)
			// repeat at 1
			// old: new CycleSubtletySetup[]{sd_w_sb,no_sd_w_sb,sd,no_sd,sd,no_sd}
			double full_cycle_length = current_sd_cd;
			double active_sd_cd = 0;
			while(full_cycle_length > 0.001){
				CycleSubtletySetup cycle = null;
				if(active_sd_cd <= 0){
					cycle = sd;
					active_sd_cd = current_sd_cd;
				}else{
					cycle = no_sd;
				}
				active_sd_cd -= cycle.length;
				cycle.length = Math.min(full_cycle_length, cycle.length);
				full_cycle_length -= cycle.length;
			}
			
			KeyValueStore template_sd = get_cycle_aps(sd);
			KeyValueStore template_no_sd = get_cycle_aps(no_sd);
			
			for(String attack : template_sd.keySet()){
				aps.add(attack, template_sd.get(attack) * (sd_duration/current_sd_cd));
			}
			
			for(String attack : template_no_sd.keySet()){
				aps.add(attack, template_no_sd.get(attack) * ((current_sd_cd-sd_duration)/current_sd_cd));
			}
			
			if(stats.getSettings().subtlety_cpg == SubtletyCPG.HEMORRHAGE_ONLY)
				aps.add("hemorrhage_dot", 1. / 3);
			aps.add("rupture", 1./2 );
			
			double mh_aps = aps.get("mh") + aps.get("backstab") + aps.get("hemorrhage") + aps.get("eviscerate") + aps.get("ambush");
			double oh_aps = aps.get("oh");
			
			double ip_aps = mh_aps * 0.3;
			ip_aps += oh_aps * 0.3;
			
			aps.add("instant_poison", ip_aps);
			aps.add("deadly_poison", 1./3);
			
			this.procs(mh_aps, oh_aps, ip_aps);
			
			if(Math.abs(last_aps - aps.sum()) <  PRECISION)
				break;
			last_aps = aps.sum();
		}
		
		find_weakness_uptime += (10 + sd_duration) * 1. / current_sd_cd;
		
		// find weakness
		find_weakness_damage_boost = 1.;
        find_weakness_multiplier = 1.;
        double armor_value = RATING.BOSS_ARMOR;
        double find_weakness_reduction = 1.;
        double armor_reduction = (1 - find_weakness_reduction);
        find_weakness_damage_boost = stats.getEffectiveDamage(armor_reduction * armor_value) / stats.getEffectiveDamage();
        find_weakness_multiplier = 1 + (find_weakness_damage_boost - 1) * find_weakness_uptime;
//        System.out.println("find_weakness_damage_boost " + find_weakness_damage_boost);
//        System.out.println("find_weakness_uptime " + find_weakness_uptime);
//        System.out.println("find_weakness_multiplier " + find_weakness_multiplier);
        
        double mos_value = .1;
        // perk enhanced master of subtlety
        if(stats.getSettings().player_level >= 100) {
        	mos_value += .05;
        }
        double mos_triggers = 1 / current_vanish_cd;
        double prep = 1.;
        mos_triggers += prep / stats.getSettings().FIGHT_LENGTH;
        mos_multiplier = 1. + mos_value * 6 * mos_triggers;
//		System.out.println("mos_multiplier " + mos_multiplier);
        
        // sinister calling multistrike dot procs
        double ms_chance = stats.getMultistrikeChance();
        double valid_attacks = aps.get("ambush") + aps.get("backstab");
        double extra_ticks_per_sec = 2 * ms_chance * valid_attacks;
        aps.add("rupture", extra_ticks_per_sec);
        aps.add("hemorrhage_dot", extra_ticks_per_sec);
	}
	
	private KeyValueStore get_cycle_aps(CycleSubtletySetup cycle) {
		KeyValueStore aps = new KeyValueStore();
		
		double base_evi_ec = 35.;
		// perk enhanced eviscerate
		if(stats.getSettings().player_level >= 100)
			base_evi_ec -= 5.;
		
		//System.out.println(cycle.name + " " + cycle.length);
		double snd_speed = 1. + 0.4 * (1 + stats.get_subtlety_mastery_multiplier());	
		double total_speed = this.calc_total_speed(snd_speed);
		
		double mh_hits_per_sec = 0.;
		double oh_hits_per_sec = 0.;
		double mh_swing_per_sec = total_speed / stats.getMh().getSpeed();
		double oh_swing_per_sec = total_speed / stats.getOh().getSpeed();
		mh_hits_per_sec = mh_swing_per_sec * this.stats.getMhHitChance();
		oh_hits_per_sec = oh_swing_per_sec * this.stats.getOhHitChance();
		aps.add("mh", mh_hits_per_sec);
		aps.add("oh", oh_hits_per_sec);
		
		double er_regen = 8. / 2.;
		double energy_regen = cycle.energy_regen + er_regen;
		
		double finisher_size = 5.;
		String cpg = "backstab";
		double cp_per_cpg = 1;
		double base_ec_cpg = 35;
		if(stats.getSettings().subtlety_cpg == SubtletyCPG.HEMORRHAGE_ONLY){
			cpg = "hemorrhage";
			cp_per_cpg = 1;
			base_ec_cpg = 30;
		}
		if(cpg.equals("backstab") && stats.hasSetBonus(SetBonus.T16_4PC)){
			base_ec_cpg += (60 - base_ec_cpg) * 0.04;
			cp_per_cpg += 0.04;
			//System.out.println("t16...4pc new backstab ec " + base_ec_cpg);
		}
		if(cycle.shadow_dance){
			cpg = "ambush";
			cp_per_cpg = 2;
			base_ec_cpg = 40;
		}
		double ec_cpg = stats.getRealEnergyCost(base_ec_cpg);
		double avg_cpg_per_finisher = finisher_size / cp_per_cpg;
		
		double ec_snd = 25;
		double ec_evi = stats.getRealEnergyCost(base_evi_ec);
		double ec_rup = stats.getRealEnergyCost(25);
		
		double avg_relentless_strikes = 5. * finisher_size;
		
		double snd_duration = 6 + 6 * finisher_size;
		double rup_duration = 4 + 4 * finisher_size;
		if(stats.hasSetBonus(SetBonus.T15_2PC)){
			snd_duration += 6;
			rup_duration += 4;
		}
		
		double energy_cost = avg_cpg_per_finisher * ec_cpg;
		double energy_cost_rup = energy_cost + ec_rup - avg_relentless_strikes;
		double energy_cost_evi = energy_cost + ec_evi - avg_relentless_strikes;
		double energy_cost_snd = energy_cost + ec_snd - avg_relentless_strikes;
		
		double ruptures = 1. / rup_duration;
		if(cycle.shadow_dance && !stats.getSettings().use_rupture_during_shadow_dance)
			ruptures = 0.;
		double snds = 1. / snd_duration;
		if(cycle.shadow_dance && !stats.getSettings().use_snd_during_shadow_dance)
			snds = 0.;
		double energy_regen_left_for_evi = energy_regen - ruptures * energy_cost_rup;
		energy_regen_left_for_evi -= snds * energy_cost_snd;
		
		double aps_so_far = (ruptures + snds) * (1 + avg_cpg_per_finisher);
		double gcd = 1.0;
		
		double aps_free = 1. / gcd;
		double aps_left = aps_free - aps_so_far;
		
		double evis_per_sec = energy_regen_left_for_evi / energy_cost_evi;
		double evis_build_up_aps = evis_per_sec * (1 + avg_cpg_per_finisher);
		double cpg_per_sec = evis_per_sec * avg_cpg_per_finisher;
		
		if(aps_left < evis_build_up_aps){
			double mod = evis_build_up_aps / aps_left;
			cpg_per_sec = cpg_per_sec / mod;
			evis_per_sec = evis_per_sec / mod;
		}

		cpg_per_sec += (ruptures + snds) * avg_cpg_per_finisher;
		double rupture_applications_per_sec = ruptures;
		aps.add("rupture_applications", rupture_applications_per_sec);
		
		if(cpg.equals("backstab") && stats.hasSetBonus(SetBonus.T16_4PC)){
			aps.add("ambush", cpg_per_sec * 0.04);
			find_weakness_uptime += 10 * cpg_per_sec * 0.04;
			cpg_per_sec -= cpg_per_sec * 0.04;
		}
		aps.add(cpg, cpg_per_sec);
		aps.add("eviscerate", evis_per_sec);
		return aps;
	}

	@Override
	protected double getSpecDamageMod(AttackData ab) {
		double dmgMod = 1.;
		dmgMod *= 1.2; // Sanguinary Vein
		dmgMod *= mos_multiplier;
		if(Arrays.asList("ambush").contains( ab.getAttack() )){
			dmgMod *= this.find_weakness_damage_boost;
			dmgMod *= 1 + .1 * ambushes_from_vanish / aps.get("ambush");
			if(stats.hasTalent(Talent.nightstalker))
				dmgMod *= 1 + .50 * ambushes_from_vanish / aps.get("ambush");				
		}else if(ab.getType() == AttackType.PHYSICAL){
			dmgMod *= this.find_weakness_multiplier;
		}
		if(Arrays.asList("eviscerate","rupture").contains( ab.getAttack() )){
			dmgMod *= (1 + stats.get_subtlety_mastery_multiplier());
		}
		return dmgMod;
	}

}
